/*************************************************************************\
*                  Copyright (C) Michael Kerrisk, 2020.                   *
*                                                                         *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the   *
* Free Software Foundation, either version 3 or (at your option) any      *
* later version. This program is distributed without any warranty.  See   *
* the file COPYING.gpl-v3 for details.                                    *
\*************************************************************************/

/* Listing 32-2 */

/* thread_cleanup.c

   An example of thread cancellation using the POSIX threads API:
   demonstrate the use of pthread_cancel() and cleanup handlers.
*/
#include <pthread.h>
#include "tlpi_hdr.h"

static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static int glob = 0;                    /* Predicate variable */

static void     /* Free memory pointed to by 'arg' and unlock mutex */
cleanupHandler(void *arg)
{
	int s;

	printf("cleanup: freeing block at %p\n", arg);
	free(arg);                                                            // (1)

	printf("cleanup: unlocking mutex\n");
	s = pthread_mutex_unlock(&mtx);                                       // (2)
	if (s != 0)
		errExitEN(s, "pthread_mutex_unlock");
}

static void *
threadFunc(void *arg)
{
	int s;
	void *buf = NULL;             /* Buffer allocated by thread */

	buf = malloc(0x10000);        /* Not a cancellation point */          // (3)
	printf("thread:  allocated memory at %p\n", buf);

	s = pthread_mutex_lock(&mtx);       /* Not a cancellation point */    // (4)
	if (s != 0)
		errExitEN(s, "pthread_mutex_lock");

	pthread_cleanup_push(cleanupHandler, buf);                            // (5)

	while (glob == 0) {
		s = pthread_cond_wait(&cond, &mtx);  /* A cancellation point */   // (6)
		if (s != 0)
			errExitEN(s, "pthread_cond_wait");
	}

	printf("thread:  condition wait loop completed\n");
	pthread_cleanup_pop(1);             /* Executes cleanup handler */    // (7)
	return NULL;
}

int
main(int argc, char *argv[])
{
	pthread_t thr;
	void *res;
	int s;

	s = pthread_create(&thr, NULL, threadFunc, NULL);                     // (8)
	if (s != 0)
		errExitEN(s, "pthread_create");

	sleep(2);             /* Give thread a chance to get started */

	if (argc == 1) {      /* Cancel thread */
		printf("main:    about to cancel thread\n");
		s = pthread_cancel(thr);                                          // (9)
		if (s != 0)
			errExitEN(s, "pthread_cancel");

	} else {              /* Signal condition variable */
		printf("main:    about to signal condition variable\n");

		s = pthread_mutex_lock(&mtx);   /* See the TLPI page 679 erratum */
		if (s != 0)
			errExitEN(s, "pthread_mutex_lock");

		glob = 1;

		s = pthread_mutex_unlock(&mtx); /* See the TLPI page 679 erratum */
		if (s != 0)
			errExitEN(s, "pthread_mutex_unlock");

		s = pthread_cond_signal(&cond);                                   // (10)
		if (s != 0)
			errExitEN(s, "pthread_cond_signal");
	}

	s = pthread_join(thr, &res);                                          // (11)
	if (s != 0)
		errExitEN(s, "pthread_join");
	if (res == PTHREAD_CANCELED)
		printf("main:    thread was canceled\n");
	else
		printf("main:    thread terminated normally\n");

	exit(EXIT_SUCCESS);
}
